/*
* @(#)DrawApplication.java 5.1
*
*/
package CH.ifa.draw.application;
import java.awt.Adjustable;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FileDialog;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Menu;
import java.awt.MenuBar;
import java.awt.MenuItem;
import java.awt.MenuShortcut;
import java.awt.Panel;
import java.awt.Point;
import java.awt.PrintJob;
import java.awt.ScrollPane;
import java.awt.TextField;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import CH.ifa.draw.figures.GroupCommand;
import CH.ifa.draw.figures.PolyLineFigure;
import CH.ifa.draw.figures.UngroupCommand;
import CH.ifa.draw.framework.Drawing;
import CH.ifa.draw.framework.DrawingEditor;
import CH.ifa.draw.framework.DrawingView;
import CH.ifa.draw.framework.Tool;
import CH.ifa.draw.standard.AlignCommand;
import CH.ifa.draw.standard.BringToFrontCommand;
import CH.ifa.draw.standard.BufferedUpdateStrategy;
import CH.ifa.draw.standard.ChangeAttributeCommand;
import CH.ifa.draw.standard.CopyCommand;
import CH.ifa.draw.standard.CutCommand;
import CH.ifa.draw.standard.DeleteCommand;
import CH.ifa.draw.standard.DuplicateCommand;
import CH.ifa.draw.standard.PasteCommand;
import CH.ifa.draw.standard.SelectionTool;
import CH.ifa.draw.standard.SendToBackCommand;
import CH.ifa.draw.standard.SimpleUpdateStrategy;
import CH.ifa.draw.standard.StandardDrawing;
import CH.ifa.draw.standard.StandardDrawingView;
import CH.ifa.draw.standard.ToggleGridCommand;
import CH.ifa.draw.standard.ToolButton;
import CH.ifa.draw.util.ColorMap;
import CH.ifa.draw.util.CommandMenu;
import CH.ifa.draw.util.Iconkit;
import CH.ifa.draw.util.PaletteButton;
import CH.ifa.draw.util.PaletteLayout;
import CH.ifa.draw.util.PaletteListener;
import CH.ifa.draw.util.StorableInput;
import CH.ifa.draw.util.StorableOutput;
/**
* DrawApplication defines a standard presentation for
* standalone drawing editors. The presentation is
* customized in subclasses.
* The application is started as follows:
* <pre>
* public static void main(String[] args) {
* MayDrawApp window = new MyDrawApp();
* window.open();
* }
* </pre>
*/
public class DrawApplication
extends Frame
implements DrawingEditor, PaletteListener {
private Drawing fDrawing;
private Tool fTool;
private Iconkit fIconkit;
private TextField fStatusLine;
private StandardDrawingView fView;
private ToolButton fDefaultToolButton;
private ToolButton fSelectedToolButton;
private String fDrawingFilename;
static String fgUntitled = "untitled";
// the image resource path
private static final String fgDrawPath = "/CH/ifa/draw/";
public static final String IMAGES = fgDrawPath+"images/";
/**
* The index of the file menu in the menu bar.
*/
public static final int FILE_MENU = 0;
/**
* The index of the edit menu in the menu bar.
*/
public static final int EDIT_MENU = 1;
/**
* The index of the alignment menu in the menu bar.
*/
public static final int ALIGNMENT_MENU = 2;
/**
* The index of the attributes menu in the menu bar.
*/
public static final int ATTRIBUTES_MENU = 3;
/**
* Constructs a drawing window with a default title.
*/
public DrawApplication() {
super("JHotDraw");
}
/**
* Constructs a drawing window with the given title.
*/
public DrawApplication(String title) {
super(title);
}
/**
* Opens the window and initializes its contents.
* Clients usually only call but don't override it.
*/
public void open() {
fIconkit = new Iconkit(this);
setLayout(new BorderLayout());
fView = createDrawingView();
Component contents = createContents(fView);
add("Center", contents);
//add("Center", fView);
Panel tools = createToolPalette();
createTools(tools);
add("West", tools);
fStatusLine = createStatusLine();
add("South", fStatusLine);
MenuBar mb = new MenuBar();
createMenus(mb);
setMenuBar(mb);
initDrawing();
Dimension d = defaultSize();
setSize(d.width, d.height);
addListeners();
show();
}
/**
* Registers the listeners for this window
*/
protected void addListeners() {
addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent event) {
exit();
}
}
);
}
private void initDrawing() {
fDrawing = createDrawing();
fDrawingFilename = fgUntitled;
fView.setDrawing(fDrawing);
toolDone();
}
/**
* Creates the standard menus. Clients override this
* method to add additional menus.
*/
protected void createMenus(MenuBar mb) {
mb.add(createFileMenu());
mb.add(createEditMenu());
mb.add(createAlignmentMenu());
mb.add(createAttributesMenu());
mb.add(createDebugMenu());
}
/**
* Creates the file menu. Clients override this
* method to add additional menu items.
*/
protected Menu createFileMenu() {
Menu menu = new Menu("File");
MenuItem mi = new MenuItem("New", new MenuShortcut('n'));
mi.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent event) {
promptNew();
}
}
);
menu.add(mi);
mi = new MenuItem("Open...", new MenuShortcut('o'));
mi.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent event) {
promptOpen();
}
}
);
menu.add(mi);
mi = new MenuItem("Save As...", new MenuShortcut('s'));
mi.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent event) {
promptSaveAs();
}
}
);
menu.add(mi);
mi = new MenuItem("Save As Serialized...");
mi.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent event) {
promptSaveAsSerialized();
}
}
);
menu.add(mi);
menu.addSeparator();
mi = new MenuItem("Print...", new MenuShortcut('p'));
mi.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent event) {
print();
}
}
);
menu.add(mi);
menu.addSeparator();
mi = new MenuItem("Exit");
mi.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent event) {
exit();
}
}
);
menu.add(mi);
return menu;
}
/**
* Creates the edit menu. Clients override this
* method to add additional menu items.
*/
protected Menu createEditMenu() {
CommandMenu menu = new CommandMenu("Edit");
menu.add(new CutCommand("Cut", fView), new MenuShortcut('x'));
menu.add(new CopyCommand("Copy", fView), new MenuShortcut('c'));
menu.add(new PasteCommand("Paste", fView), new MenuShortcut('v'));
menu.addSeparator();
menu.add(new DuplicateCommand("Duplicate", fView), new MenuShortcut('d'));
menu.add(new DeleteCommand("Delete", fView));
menu.addSeparator();
menu.add(new GroupCommand("Group", fView));
menu.add(new UngroupCommand("Ungroup", fView));
menu.addSeparator();
menu.add(new SendToBackCommand("Send to Back", fView));
menu.add(new BringToFrontCommand("Bring to Front", fView));
return menu;
}
/**
* Creates the alignment menu. Clients override this
* method to add additional menu items.
*/
protected Menu createAlignmentMenu() {
CommandMenu menu = new CommandMenu("Align");
menu.add(new ToggleGridCommand("Toggle Snap to Grid", fView, new Point(4,4)));
menu.addSeparator();
menu.add(new AlignCommand("Lefts", fView, AlignCommand.LEFTS));
menu.add(new AlignCommand("Centers", fView, AlignCommand.CENTERS));
menu.add(new AlignCommand("Rights", fView, AlignCommand.RIGHTS));
menu.addSeparator();
menu.add(new AlignCommand("Tops", fView, AlignCommand.TOPS));
menu.add(new AlignCommand("Middles", fView, AlignCommand.MIDDLES));
menu.add(new AlignCommand("Bottoms", fView, AlignCommand.BOTTOMS));
return menu;
}
/**
* Creates the debug menu. Clients override this
* method to add additional menu items.
*/
protected Menu createDebugMenu() {
Menu menu = new Menu("Debug");
MenuItem mi = new MenuItem("Simple Update");
mi.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent event) {
fView.setDisplayUpdate(new SimpleUpdateStrategy());
}
}
);
menu.add(mi);
mi = new MenuItem("Buffered Update");
mi.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent event) {
fView.setDisplayUpdate(new BufferedUpdateStrategy());
}
}
);
menu.add(mi);
return menu;
}
/**
* Creates the attributes menu and its submenus. Clients override this
* method to add additional menu items.
*/
protected Menu createAttributesMenu() {
Menu menu = new Menu("Attributes");
menu.add(createColorMenu("Fill Color", "FillColor"));
menu.add(createColorMenu("Pen Color", "FrameColor"));
menu.add(createArrowMenu());
menu.addSeparator();
menu.add(createFontMenu());
menu.add(createFontSizeMenu());
menu.add(createFontStyleMenu());
menu.add(createColorMenu("Text Color", "TextColor"));
return menu;
}
/**
* Creates the color menu.
*/
protected Menu createColorMenu(String title, String attribute) {
CommandMenu menu = new CommandMenu(title);
for (int i=0; i<ColorMap.size(); i++)
menu.add(
new ChangeAttributeCommand(
ColorMap.name(i),
attribute,
ColorMap.color(i),
fView
)
);
return menu;
}
/**
* Creates the arrows menu.
*/
protected Menu createArrowMenu() {
CommandMenu menu = new CommandMenu("Arrow");
menu.add(new ChangeAttributeCommand("none", "ArrowMode", new Integer(PolyLineFigure.ARROW_TIP_NONE), fView));
menu.add(new ChangeAttributeCommand("at Start", "ArrowMode", new Integer(PolyLineFigure.ARROW_TIP_START), fView));
menu.add(new ChangeAttributeCommand("at End", "ArrowMode", new Integer(PolyLineFigure.ARROW_TIP_END), fView));
menu.add(new ChangeAttributeCommand("at Both", "ArrowMode", new Integer(PolyLineFigure.ARROW_TIP_BOTH), fView));
return menu;
}
/**
* Creates the fonts menus. It installs all available fonts
* supported by the toolkit implementation.
*/
protected Menu createFontMenu() {
CommandMenu menu = new CommandMenu("Font");
String fonts[] = Toolkit.getDefaultToolkit().getFontList();
for (int i = 0; i < fonts.length; i++)
menu.add(new ChangeAttributeCommand(fonts[i], "FontName", fonts[i], fView));
return menu;
}
/**
* Creates the font style menu with entries (Plain, Italic, Bold).
*/
protected Menu createFontStyleMenu() {
CommandMenu menu = new CommandMenu("Font Style");
menu.add(new ChangeAttributeCommand("Plain", "FontStyle", new Integer(Font.PLAIN), fView));
menu.add(new ChangeAttributeCommand("Italic","FontStyle", new Integer(Font.ITALIC),fView));
menu.add(new ChangeAttributeCommand("Bold", "FontStyle", new Integer(Font.BOLD), fView));
return menu;
}
/**
* Creates the font size menu.
*/
protected Menu createFontSizeMenu() {
CommandMenu menu = new CommandMenu("Font Size");
int sizes[] = { 9, 10, 12, 14, 18, 24, 36, 48, 72 };
for (int i = 0; i < sizes.length; i++) {
menu.add(
new ChangeAttributeCommand(
Integer.toString(sizes[i]),
"FontSize",
new Integer(sizes[i]), fView)
);
}
return menu;
}
/**
* Creates the tool palette.
*/
protected Panel createToolPalette() {
Panel palette = new Panel();
palette.setBackground(Color.lightGray);
palette.setLayout(new PaletteLayout(2,new Point(2,2)));
return palette;
}
/**
* Creates the tools. By default only the selection tool is added.
* Override this method to add additional tools.
* Call the inherited method to include the selection tool.
* @param palette the palette where the tools are added.
*/
protected void createTools(Panel palette) {
Tool tool = createSelectionTool();
fDefaultToolButton = createToolButton(IMAGES+"SEL", "Selection Tool", tool);
palette.add(fDefaultToolButton);
}
/**
* Creates the selection tool used in this editor. Override to use
* a custom selection tool.
*/
protected Tool createSelectionTool() {
return new SelectionTool(view());
}
/**
* Creates a tool button with the given image, tool, and text
*/
protected ToolButton createToolButton(String iconName, String toolName, Tool tool) {
return new ToolButton(this, iconName, toolName, tool);
}
/**
* Creates the drawing view used in this application.
* You need to override this method to use a DrawingView
* subclass in your application. By default a standard
* DrawingView is returned.
*/
protected StandardDrawingView createDrawingView() {
Dimension d = getDrawingViewSize();
return new StandardDrawingView(this, d.width, d.height);
}
/**
* Override to define the dimensions of the drawing view.
*/
protected Dimension getDrawingViewSize() {
return new Dimension(400, 600);
}
/**
* Creates the drawing used in this application.
* You need to override this method to use a Drawing
* subclass in your application. By default a standard
* Drawing is returned.
*/
protected Drawing createDrawing() {
return new StandardDrawing();
}
/**
* Creates the contents component of the application
* frame. By default the DrawingView is returned in
* a ScrollPane.
*/
protected Component createContents(StandardDrawingView view) {
ScrollPane sp = new ScrollPane();
Adjustable vadjust = sp.getVAdjustable();
Adjustable hadjust = sp.getHAdjustable();
hadjust.setUnitIncrement(16);
vadjust.setUnitIncrement(16);
sp.add(view);
return sp;
}
/**
* Sets the drawing to be edited.
*/
public void setDrawing(Drawing drawing) {
fView.setDrawing(drawing);
fDrawing = drawing;
}
/**
* Gets the default size of the window.
*/
protected Dimension defaultSize() {
return new Dimension(430,406);
}
/**
* Creates the status line.
*/
protected TextField createStatusLine() {
TextField field = new TextField("No Tool", 40);
field.setEditable(false);
return field;
}
/**
* Handles a user selection in the palette.
* @see PaletteListener
*/
public void paletteUserSelected(PaletteButton button) {
ToolButton toolButton = (ToolButton) button;
setTool(toolButton.tool(), toolButton.name());
setSelected(toolButton);
}
/**
* Handles when the mouse enters or leaves a palette button.
* @see PaletteListener
*/
public void paletteUserOver(PaletteButton button, boolean inside) {
ToolButton toolButton = (ToolButton) button;
if (inside)
showStatus(toolButton.name());
else
showStatus(fSelectedToolButton.name());
}
/**
* Gets the current drawing.
* @see DrawingEditor
*/
public Drawing drawing() {
return fDrawing;
}
/**
* Gets the current tool.
* @see DrawingEditor
*/
public Tool tool() {
return fTool;
}
/**
* Gets the current drawing view.
* @see DrawingEditor
*/
public DrawingView view() {
return fView;
}
/**
* Sets the default tool of the editor.
* @see DrawingEditor
*/
public void toolDone() {
if (fDefaultToolButton != null) {
setTool(fDefaultToolButton.tool(), fDefaultToolButton.name());
setSelected(fDefaultToolButton);
}
}
/**
* Handles a change of the current selection. Updates all
* menu items that are selection sensitive.
* @see DrawingEditor
*/
public void selectionChanged(DrawingView view) {
MenuBar mb = getMenuBar();
CommandMenu editMenu = (CommandMenu)mb.getMenu(EDIT_MENU);
editMenu.checkEnabled();
CommandMenu alignmentMenu = (CommandMenu)mb.getMenu(ALIGNMENT_MENU);
alignmentMenu.checkEnabled();
}
/**
* Shows a status message.
* @see DrawingEditor
*/
public void showStatus(String string) {
fStatusLine.setText(string);
}
private void setTool(Tool t, String name) {
if (fTool != null)
fTool.deactivate();
fTool = t;
if (fTool != null) {
fStatusLine.setText(name);
fTool.activate();
}
}
private void setSelected(ToolButton button) {
if (fSelectedToolButton != null)
fSelectedToolButton.reset();
fSelectedToolButton = button;
if (fSelectedToolButton != null)
fSelectedToolButton.select();
}
/**
* Exits the application. You should never override this method
*/
public void exit() {
destroy();
setVisible(false); // hide the Frame
dispose(); // tell windowing system to free resources
System.exit(0);
}
/**
* Handles additional clean up operations. Override to destroy
* or release drawing editor resources.
*/
protected void destroy() {
}
/**
* Resets the drawing to a new empty drawing.
*/
public void promptNew() {
initDrawing();
}
/**
* Shows a file dialog and opens a drawing.
*/
public void promptOpen() {
FileDialog dialog = new FileDialog(this, "Open File...", FileDialog.LOAD);
dialog.show();
String filename = dialog.getFile();
if (filename != null) {
filename = stripTrailingAsterisks(filename);
String dirname = dialog.getDirectory();
loadDrawing(dirname + filename);
}
dialog.dispose();
}
/**
* Shows a file dialog and saves drawing.
*/
public void promptSaveAs() {
toolDone();
String path = getSavePath("Save File...");
if (path != null) {
if (!path.endsWith(".draw"))
path += ".draw";
saveAsStorableOutput(path);
}
}
/**
* Shows a file dialog and saves drawing.
*/
public void promptSaveAsSerialized() {
toolDone();
String path = getSavePath("Save File...");
if (path != null) {
if (!path.endsWith(".ser"))
path += ".ser";
saveAsObjectOutput(path);
}
}
/**
* Prints the drawing.
*/
public void print() {
fTool.deactivate();
PrintJob printJob = getToolkit().getPrintJob(this, "Print Drawing", null);
if (printJob != null) {
Graphics pg = printJob.getGraphics();
if (pg != null) {
fView.printAll(pg);
pg.dispose(); // flush page
}
printJob.end();
}
fTool.activate();
}
private String getSavePath(String title) {
String path = null;
FileDialog dialog = new FileDialog(this, title, FileDialog.SAVE);
dialog.show();
String filename = dialog.getFile();
if (filename != null) {
filename = stripTrailingAsterisks(filename);
String dirname = dialog.getDirectory();
path = dirname + filename;
}
dialog.dispose();
return path;
}
private String stripTrailingAsterisks(String filename) {
// workaround for bug on NT
if (filename.endsWith("*.*"))
return filename.substring(0, filename.length() - 4);
else
return filename;
}
private void saveAsStorableOutput(String file) {
// TBD: should write a MIME header
try {
FileOutputStream stream = new FileOutputStream(file);
StorableOutput output = new StorableOutput(stream);
output.writeStorable(fDrawing);
output.close();
} catch (IOException e) {
showStatus(e.toString());
}
}
private void saveAsObjectOutput(String file) {
// TBD: should write a MIME header
try {
FileOutputStream stream = new FileOutputStream(file);
ObjectOutput output = new ObjectOutputStream(stream);
output.writeObject(fDrawing);
output.close();
} catch (IOException e) {
showStatus(e.toString());
}
}
private void loadDrawing(String file) {
toolDone();
String type = guessType(file);
if (type.equals("storable"))
readFromStorableInput(file);
else if (type.equals("serialized"))
readFromObjectInput(file);
else
showStatus("Unknown file type");
}
private void readFromStorableInput(String file) {
try {
FileInputStream stream = new FileInputStream(file);
StorableInput input = new StorableInput(stream);
fDrawing.release();
fDrawing = (Drawing)input.readStorable();
fView.setDrawing(fDrawing);
} catch (IOException e) {
initDrawing();
showStatus("Error: " + e);
}
}
private void readFromObjectInput(String file) {
try {
FileInputStream stream = new FileInputStream(file);
ObjectInput input = new ObjectInputStream(stream);
fDrawing.release();
fDrawing = (Drawing)input.readObject();
fView.setDrawing(fDrawing);
} catch (IOException e) {
initDrawing();
showStatus("Error: " + e);
} catch (ClassNotFoundException e) {
initDrawing();
showStatus("Class not found: " + e);
}
}
private String guessType(String file) {
if (file.endsWith(".draw"))
return "storable";
if (file.endsWith(".ser"))
return "serialized";
return "unknown";
}
}